(defpackage :selection-task
  (:use :cl)
  (:export :selection
           :read-problems
           :selection-click
           :submit-answer))

(in-package :selection-task)

(gwee:define-component selection (exp:stimulus-input-feedback)
  ((tree :initarg :tree :accessor tree)
   (reward :initform 4 :initarg :reward :accessor reward))
  (:views
   (diagram new-tree:new-tree :accessor diagram)
   (done gwee:button :width 100 :height 40 :data "Done")
   ;;
   (divider gwee:divider
            :margin-bottom 0
            :direction :vertical
            :foreground (gwee:gray 150))
   (subject-text gwee:text-area :y :top :x :center :margin-top 50 :data "Your Answer" :font (gwee:open-font "sans.ttf" 46))
   (answer-text gwee:text-area :y :top :x :center :margin-top 50 :data "Correct Answer" :font (gwee:open-font "sans.ttf" 46))
   (feedback gwee:text-area :left-padding 20 :right-padding 20 :top-padding 10 :bottom-padding 10 :border-color (gwee:gray 150) :y :center :x :center :data " " :font (gwee:open-font "sans.ttf" 46) :name :feedback))
  (:layouts
   (default gwee:switchable-layout
       (problem feedback-overlay))
   (problem gwee:column-layout
            (diagram done)
            :margin-bottom 50
            :margin-top 50)
   (feedback-overlay gwee:view
                     (feedback-layout feedback))
   (feedback-layout gwee:row-layout
             (subject-diagram divider correct-diagram))
   (subject-diagram new-tree:new-tree
                    (subject-text)
                    :accessor subject-diagram)
   (correct-diagram new-tree:new-tree
                    (answer-text)
                    :accessor correct-diagram))
  (:default-initargs
    :task-time 60))

(defmethod exp:state-dump append (exp (state selection))
  `(:tree ,(tree state)
    :reward ,(reward state)))

(defun get-box (node)
  (if (and (typep node 'd:argument)
           (d:quantity node))
      (tree:parent node)
      node))

(defun event-callback (view event)
  (let ((c (gwee:component-for-view view)))
    (exp:state-dispatch-event exp:*experiment* c event)))

(defmethod e::translate-click-node (exp (state selection) &key node)
  (make-instance 'e:click-event
                 :target (d:find-id-or-die (d:root (diagram state))
                                            node)))

(defmethod e::translate-enter (exp (state selection) &key node)
  (make-instance 'e:motion-event
                 :leave nil
                 :enter (d:find-id-or-die (d:root (diagram state))
                                           node)))

(exp:define-state-match problem-mode ((state selection))
    (with-slots (default problem) state
      (eq (gwee:switchable-layout-current-view default) problem)))

(exp:define-state-match feedback-mode ((state selection))
    (with-slots (default feedback-overlay) state
      (eq (gwee:switchable-layout-current-view default) feedback-overlay)))

(exp:define-action selection-click ((state selection)
                                    (e:click-node :var node-id :key :id)
                                    (problem-mode))
  (let* ((node (d:find-id-or-die (d:root (diagram state)) node-id))
         (box (get-box node)))
    (if (typep box 'd:box)
        (progn
          (setf (d:selected box) (not (d:selected box)))
          (exp:lg (if (d:selected box) 'select 'unselect) :node (d:id box))
          (d:update-diagram-view (diagram state)))
        (exp:lg 'click :node (d:id box)))))

(exp:define-action highlight-node ((state selection)
                                   (e:mouse-motion :var motion)
                                   (problem-mode))
  (when (or (getf motion :enter)
            (getf motion :leave))
    (let ((leave (when (getf motion :leave)
                   (get-box (d:find-id-or-die (d:root (diagram state)) (getf motion :leave)))))
          (enter (when (getf motion :enter)
                   (get-box (d:find-id-or-die (d:root (diagram state)) (getf motion :enter))))))
      (d:clear-highlights (d:root (diagram state)))
      (unless (eq enter leave)
        (when leave (exp:lg 'leave :node (d:id leave)))
        (when enter (exp:lg 'enter :node (d:id enter))))
      (when enter
        (setf (d:highlighted enter) t))
      (setf (gwee:view-needs-update-p (diagram state)) t))))

(defun convert (tree)
  (append (dgraph:convert tree)
          (dgraph:attributes tree)))

(defmethod exp:state-list-objects append (exp (state selection))
  (with-slots (diagram default problem feedback subject-diagram correct-diagram) state
    (if (print (eq (gwee:switchable-layout-current-view default) problem))
        (list :buttons '(:done)
              :tree (convert (d:root diagram)))
        (list :buttons nil
              :subject-diagram (convert (d:root subject-diagram))
              :correct-diagram (convert (d:root correct-diagram))
              :feedback (gwee:view-data feedback)))))

(defmethod exp:state-list-actions append (experiment (state selection))
  (exp::default-list-actions state))

(defmethod exp:state-init (exp (state selection))
  (with-slots (done diagram tree correct-diagram) state
    (setf (d:root diagram) (tree:recreate-parent-slots (d:merge-by-id (sf:equation-to-tree tree)))
          (d:root correct-diagram) (tree:copy-node (d:root diagram) t)
          (d:event-callback diagram) 'event-callback
          (gwee:click-callback done)
          (exp:dispatch-event-fn exp state exp:click-event :source :done))
    ;; create correct diagram
    (let* ((l (find-if 'lt::minimal-loop-p (lt::find-all-loops (d:root correct-diagram))))
           (selection-needed (and l (remove-if-not (lambda (n) (typep n 'd:box))
                                                   (lt::box-loop-boxes l)))))
      (dolist (i selection-needed)
        (setf (d:selected i) t)))
    (exp:lg 'start :equation tree :tree (u:sharp-dot (d:root diagram)))
    (gwee:sync-views)
    (d:update-diagram-view diagram)))

(exp:define-action submit-answer ((state selection)
                                  (exp:click :source :done)
                                  (problem-mode))
  (exp:do-feedback state nil))

(defun correct-p (state)
  (with-slots (diagram) state
    (let* ((l (find-if 'lt::minimal-loop-p (lt::find-all-loops (d:root diagram))))
           (selection-needed (and l (remove-if-not (lambda (n) (typep n 'd:box))
                                                   (lt::box-loop-boxes l))))
           (selection (d:selected-nodes (d:root diagram))))
      (not (set-exclusive-or selection selection-needed)))))

(defmethod exp:show-answer ((state selection) timed-out)
  (with-slots (default feedback-overlay feedback diagram correct-diagram subject-diagram answer-text subject-text) state
    (let ((wrong (or timed-out (not (correct-p state)))))
      (setf (gwee:switchable-layout-current-view default) feedback-overlay)
      (exp:lg 'show-answer :correct (not wrong))
      (when (not wrong)
        (incf (exp:reward exp:*experiment*) (reward state)))
      (let (color)
        (cond
          (timed-out
           (setf (gwee:view-data feedback) "Time's Up"
                 color (gwee:color 255 0 0)))
          (wrong
           (setf (gwee:view-data feedback) "Incorrect!"
                 color (gwee:color 255 0 0)))
          (t
           (setf (gwee:view-data feedback) "Correct!"
                 color (gwee:color 0 255 0))))
        (setf (gwee:foreground-color feedback) color
              (gwee:foreground-color subject-text) color
              (gwee:foreground-color answer-text) color
              (gwee:view-needs-sync-p state) t
              (d:root subject-diagram) (d:root diagram))
        (d:update-diagram-view subject-diagram)
        (d:update-diagram-view correct-diagram)
        wrong))))

(defun read-problems (file)
  (with-open-file (s (gwee:top-level-path (format nil "transfer/~a.txt" file)))
    (loop for p = (read-line s nil s)
       until (eq p s)
       do (setf p (string-trim " 	" p))
       when (plusp (length p))
       collect `(selection :tree ,p))))
